﻿// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace System.ComponentModel.Composition
{
    public struct CompositionResult
    {
        private readonly bool _succeeded;
        private readonly ReadOnlyCollection<CompositionIssue> _issues;

        // EmptyIssues must be defined before ResultNotSucceeded otherwise it will be null when ResultNotSucceeded is initialized.
        internal static readonly IEnumerable<CompositionIssue> EmptyIssues = new ReadOnlyCollection<CompositionIssue>(new List<CompositionIssue>());
        public static readonly CompositionResult SucceededResult = new CompositionResult(true, CompositionResult.EmptyIssues);

        public bool Succeeded
        {
            get { return this._succeeded; }
        }

        public ReadOnlyCollection<CompositionIssue> Issues
        {
            get { return this._issues; }
        }

        public CompositionResult(bool succeeded, IEnumerable<CompositionIssue> issues)
        {
            if (issues == null)
                throw new ArgumentNullException("issues");

            this._succeeded = succeeded;
            this._issues = new ReadOnlyCollection<CompositionIssue>(issues.ToList<CompositionIssue>());
        }

        public override int GetHashCode()
        {
            return this.Succeeded.GetHashCode() ^
                (this.Issues == null ? 0 : this.Issues.GetHashCode());
        }

        public override bool Equals(object obj)
        {
            if (!(obj is CompositionResult))
                return false;

            return this.Equals((CompositionResult)obj);
        }

        public bool Equals(CompositionResult result)
        {
            if (!this.Succeeded.Equals(result.Succeeded))
                return false;

            if (this.Issues != null && result.Issues != null
                && this.Issues.SequenceEqual<CompositionIssue>(result.Issues))
                return true;

            return (this.Issues == null && result.Issues == null);
        }

        public static bool operator ==(CompositionResult leftCompositionResult, CompositionResult rightCompositionResult)
        {
            return leftCompositionResult.Equals(rightCompositionResult);
        }

        public static bool operator !=(CompositionResult leftCompositionResult, CompositionResult rightCompositionResult)
        {
            return !leftCompositionResult.Equals(rightCompositionResult);
        }

        internal CompositionResult MergeResult(CompositionResult result)
        {
            return new CompositionResult(
                this.Succeeded && result.Succeeded,
                this.Issues.Concat(result.Issues));
        }

        internal CompositionResult<T> ToResult<T>(T value)
        {
            return new CompositionResult<T>(this.Succeeded, this.Issues, value);
        }

        internal CompositionResult MergeIssue(CompositionIssue issue)
        {
            return new CompositionResult(
                this.Succeeded && issue.Severity != CompositionIssueSeverity.Error,
                this.Issues.Concat(new CompositionIssue[] { issue }));
        }

        internal CompositionResult MergeIssues(IEnumerable<CompositionIssue> issues)
        {
            var result = new CompositionResult(this.Succeeded, this.Issues);
            foreach (var issue in issues)
            {
                result = result.MergeIssue(issue);
            }
            return result;
        }

        internal CompositionResult MergeWarnings(CompositionResult compositionResult)
        {
            var warningIssues = compositionResult.Issues.Where(i => i.Severity == CompositionIssueSeverity.Warning);
            return this.MergeIssues(warningIssues);
        }

        internal bool ContainsOnlyOneError(string issueType)
        {
            var errorIssues = this.Issues.Where(i => i.Severity == CompositionIssueSeverity.Error);

            if (errorIssues.Count() != 1)
            {
                return false;
            }

            var issue = errorIssues.First();

            return issue.Id == issueType;
        }

        internal void ThrowOnErrors()
        {
            if (!this.Succeeded)
            {
                if (this.ContainsOnlyOneError(CompositionIssueId.CardinalityMismatch))
                {
                    throw new CardinalityMismatchException(this.Issues[0].Description);
                }

                throw new CompositionException(this.Issues);
            }
        }
    }

    public struct CompositionResult<T>
    {
        private readonly bool _succeeded;
        private readonly ReadOnlyCollection<CompositionIssue> _issues;
        private readonly T _value;

        public bool Succeeded
        {
            get { return this._succeeded; }
        }

        public ReadOnlyCollection<CompositionIssue> Issues
        {
            get { return this._issues; }
        }

        public T Value
        {
            get { return this._value; }
        }

        public CompositionResult(bool succeeded, IEnumerable<CompositionIssue> issues, T value)
        {
            if (issues == null)
                throw new ArgumentNullException("issues");

            this._succeeded = succeeded;
            this._issues = new ReadOnlyCollection<CompositionIssue>(new List<CompositionIssue>(issues));
            this._value = value;
        }

        internal CompositionResult<T> MergeIssue(CompositionIssue issue)
        {
            return new CompositionResult<T>(
                this.Succeeded && issue.Severity != CompositionIssueSeverity.Error,
                this.Issues.Concat(new CompositionIssue[] { issue }),
                this.Value);
        }

        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        public T GetValueIfSucceeded()
        {
            this.ThrowOnErrors();
            return this.Value;
        }

        public override int GetHashCode()
        {
            return this.Succeeded.GetHashCode()
                ^ (this.Issues == null ? 0 : this.Issues.GetHashCode())
                ^ (this.Value == null ? 0 : this.Value.GetHashCode());
        }

        public override bool Equals(object obj)
        {
            if (!(obj is CompositionResult<T>))
                return false;

            return this.Equals((CompositionResult<T>)obj);
        }

        public bool Equals(CompositionResult<T> result)
        {
            if (!this.Succeeded.Equals(result.Succeeded))
                return false;

            if (!object.Equals(this.Value, result.Value))
                return false;

            if (this.Issues != null && result.Issues != null
                && this.Issues.SequenceEqual<CompositionIssue>(result.Issues))
                return true;

            return (this.Issues == null && result.Issues == null);
        }

        public static bool operator ==(CompositionResult<T> leftCompositionResult, CompositionResult<T> rightCompositionResult)
        {
            return leftCompositionResult.Equals(rightCompositionResult);
        }

        public static bool operator !=(CompositionResult<T> leftCompositionResult, CompositionResult<T> rightCompositionResult)
        {
            return !leftCompositionResult.Equals(rightCompositionResult);
        }

        internal CompositionResult ToResultWithNoValue()
        {
            return new CompositionResult(this.Succeeded, this.Issues);
        }

        internal bool ContainsOnlyOneError(string issueType)
        {
            var errorIssues = this.Issues.Where(i => i.Severity == CompositionIssueSeverity.Error);

            if (errorIssues.Count() != 1)
            {
                return false;
            }

            var issue = errorIssues.First();

            return issue.Id == issueType;
        }

        internal void ThrowOnErrors()
        {
            if (!this.Succeeded)
            {
                if (this.ContainsOnlyOneError(CompositionIssueId.CardinalityMismatch))
                {
                    throw new CardinalityMismatchException(this.Issues[0].ToString());
                }

                throw new CompositionException(this.Issues);
            }
        }
    }
}
